/* @flow */
// 对对象做响应化处理
// 对数组做响应化处理
import Dep from "./dep";
import VNode from "../vdom/vnode";
import { arrayMethods } from "./array";
import {
  def,
  warn,
  hasOwn,
  hasProto,
  isObject,
  isPlainObject,
  isPrimitive,
  isUndef,
  isValidArrayIndex,
  isServerRendering,
} from "../util/index";

// 获取对象名子
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);

/**
 * In some cases we may want to disable observation inside a component's
 * update computation.
 */
export let shouldObserve: boolean = true;

export function toggleObserving(value: boolean) {
  shouldObserve = value;
}

/**
 * Observer class that is attached to each observed
 * object. Once attached, the observer converts the target
 * object's property keys into getter/setters that
 * collect dependencies and dispatch updates.
 */
export class Observer {
  // 观测对象
  value: any;
  // 依赖对象
  dep: Dep;
  // 实例计数器
  vmCount: number; // number of vms that have this object as root $data

  constructor(value: any) {
    this.value = value;
    this.dep = new Dep();
    // 初始化实例的vmcount为0
    this.vmCount = 0;
    // 将实例挂载到观测对象的__ob__数学，设置为不可举
    def(value, "__ob__", this);
    // 判断value是否是个数组
    if (Array.isArray(value)) {
      // 数组的响应式处理
      // hasProto 用来判断当前浏览器是否支持__proto__ 是兼容性的判断  __proto__ __proto__是每个对象都有的属性
      if (hasProto) {
        // arrayMethods 数组相关的方法 
        protoAugment(value, arrayMethods);
      } else {
        copyAugment(value, arrayMethods, arrayKeys);
      }
      // 为数组中的每一个对象创建一个observer实例
      this.observeArray(value);
    } else {
      // 对象响应化处理
      // 遍历对象中的每一个属性，转换成setter/getter
      this.walk(value);
    }
  }

  /**
   * Walk through all properties and convert them into
   * getter/setters. This method should only be called when
   * value type is Object.
   */
  walk(obj: Object) {
    // 获取观察对象的每一个属性
    const keys = Object.keys(obj);
    // 遍历每一个属性，设置为响应式数据
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]);
    }
  }

  /**
   * Observe a list of Array items.
   */
  observeArray(items: Array<any>) {
    for (let i = 0, l = items.length; i < l; i++) {
      // 让对应的对象转换成响应性的对象
      observe(items[i]);
    }
  }
}

// helpers

/**
 * Augment a target Object or Array by intercepting
 * the prototype chain using __proto__
 */
function protoAugment(target, src: Object) {
  /* eslint-disable no-proto */
  target.__proto__ = src;
  /* eslint-enable no-proto */
}

/**
 * Augment a target Object or Array by defining
 * hidden properties.
 */
/* istanbul ignore next */
function copyAugment(target: Object, src: Object, keys: Array<string>) {
  for (let i = 0, l = keys.length; i < l; i++) {
    const key = keys[i];
    def(target, key, src[key]);
  }
}

/**
 * Attempt to create an observer instance for a value,
 * returns the new observer if successfully observed,
 * or the existing observer if the value already has one.
 */
export function observe(value: any, asRootData: ?boolean): Observer | void {
  if (!isObject(value) || value instanceof VNode) {
    return;
  }
  let ob: Observer | void;
  if (hasOwn(value, "__ob__") && value.__ob__ instanceof Observer) {
    ob = value.__ob__;
  } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) {
    ob = new Observer(value);
  }
  if (asRootData && ob) {
    ob.vmCount++;
  }
  return ob;
}

/**
 * Define a reactive property on an Object.
 */

/**
 * 
  为一个对象定义一个响应式的属性，每一个属性对应一个 dep 对象
  如果该属性的值是对象，继续调用 observe
  如果给属性赋新值，继续调用 observe
  如果数据更新发送通知
 */
export function defineReactive(
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 1.为每一个属性、创建依赖对象实例
  const dep = new Dep();
  //  获取obj的属性描述符对象
  const property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return;
  }

  // cater for pre-defined getter/setters
  // 提供预定义的取器函数
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    val = obj[key];
  }
  // 2.判断是否递归观察子对象，并将子对象属性都转换成getter/setter 返回观察对象
  let childOb = !shallow && observe(val);
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val;
      // 如果存在当前依赖目标，即watcher对象，则建立依赖
      if (Dep.target) {
        // dep()添加相互的依赖
        // 1个组件对应一个watcher对象
        // 1个watcher会对应多个dep(要观察的属性很多)
        // 我们可以手动创建多个 watcher 监听1个属性的变化，1个dep可以对应多个watcher
        dep.depend();
        // 如果子观察目标存在，建立子对象的依赖关系，将来vue.set()会用到
        if (childOb) {
          childOb.dep.depend();
          // 如果属性是数字，则特殊处理集成数组对象依赖
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      // 返回属性值
      return value;
    },
    set: function reactiveSetter(newVal) {
      // 如果预定义的 getter 存在则 value 等于getter 调用的返回值
      // 否则直接赋予属性值
      const value = getter ? getter.call(obj) : val;

      // 如果新址等于旧值或者新值旧值为null不执行
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return;
      }
      /* eslint-enable no-self-compare */
      // 环境变量判断
      if (process.env.NODE_ENV !== "production" && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      // 如果没有getter 与 setter直接返回
      if (getter && !setter) return;
      // // 如果预定义setter存在则调用，否则直接更新新值
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      // 3. 如果新值是对象，观察子对象并返回 子的 observer 对象
      childOb = !shallow && observe(newVal);
      // 4.发布更改通知
      dep.notify();
    },
  });
}

/**
 * Set a property on an object. Adds the new property and
 * triggers change notification if the property doesn't
 * already exist.
 */
// set函数是和响应式相关的
export function set(target: Array<any> | Object, key: any, val: any): any {
  // 判断是否是undefined 或者 原始值
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }
  // 判断是否是数组 并且KEY是否是合法的索引
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key);
    // 通过splice 对 key位置进行替换
    //splice 在array.js进行了响应化处理
    target.splice(key, 1, val);
    return val;
  }
  // 如果key对象中已经在直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // 获取target中的observer对象
  const ob = (target: any).__ob__;
  // 如果target是vue 或者$data直接返回
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid adding reactive properties to a Vue instance or its root $data " +
          "at runtime - declare it upfront in the data option."
      );
    return val;
  }
  // 如果ob不存在，target不是响应式对象，直接赋值
  if (!ob) {
    target[key] = val;
    return val;
  }
  // 把key设置为响应式
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}

/**
 * Delete a property and trigger change if necessary.
 */
export function del(target: Array<any> | Object, key: any) {
  if (
    process.env.NODE_ENV !== "production" &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(
      `Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`
    );
  }
  //  判断是否是数组，以及key是否合法
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.splice(key, 1);
    return;
  }
  // 获取target的OB对象
  const ob = (target: any).__ob__;
  // target 如果是vue实例 或者$data对象，直接返回
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== "production" &&
      warn(
        "Avoid deleting properties on a Vue instance or its root $data " +
          "- just set it to null."
      );
    return;
  }
  // 如果target 对象没有key 属性直接返回
  if (!hasOwn(target, key)) {
    return;
  }
  // 删除属性
  delete target[key];
  if (!ob) {
    return;
  }

  // 通过ob 发送通知
  ob.dep.notify();
}

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray(value: Array<any>) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i];
    e && e.__ob__ && e.__ob__.dep.depend();
    if (Array.isArray(e)) {
      dependArray(e);
    }
  }
}
